# ====================================================================
# Copyright (C) 2012-2014 Eric Blond
#
# This file is part of Tungsten.
#
# Tungsten is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tungsten is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Tungsten.  If not, see <http://www.gnu.org/licenses/>.
# ====================================================================

"""
Created on Jun 2, 2012

@author: Eric Blond
"""

import collections
import weakref
import logging

#######

_STACK  = collections.deque()
_NOTIFS = {}

#######

_LOGGER = logging.getLogger('tungsten.node')

#######

class StaticCell(object):

    def __init__(self, value):
        self._cache   = value
        self._parents = weakref.WeakSet()

    def get(self):
        if _STACK:
            self._parents.add(_STACK[0])
        return self._cache

    def set(self, value):
        if value != self._cache:
            self._cache = value
            candidates = []
            for parent in self._parents:
                parent._invalidate(candidates)
            self._parents.clear()
            for candidate in candidates:
                candidate._recalculate()

#######

class CircularDependencyError(Exception):
    pass

#######

class DynamicCell(object):

    def __init__(self, function, listener = None):
        self._listener    = listener
        self._cache       = None
        self._parents     = None
        self._function    = function
        self._calculating = False
        if listener:
            self._recalculate()

    def _invalidate(self, candidates):
        if self._parents is not None:
            if self._listener:
                candidates.append(self)
            for parent in self._parents:
                parent._invalidate(candidates)
            self._parents = None

    def _recalculate(self):
        if self._parents is None:
            self._parents = weakref.WeakSet()
            _STACK.appendleft(self)
            self._calculating = True
            old_value = self._cache
            self._cache = self._function()
            self._calculating = False
            _STACK.popleft()
            if self._listener and (self._cache != old_value):
                _NOTIFS.setdefault(self, old_value)

    def get(self):
        if self._calculating:
            raise CircularDependencyError()
        self._recalculate()
        if _STACK:
            self._parents.add(_STACK[0])
        return self._cache

#######

class ActionContext(object):
    
    def __enter__(self):
        _NOTIFS.clear()
    
    def __exit__(self, exc_type, exc_value, exc_tb):
        for (node, old_value) in _NOTIFS.items():
            try:
                node._listener(old_value)
            except:
                _LOGGER.info('Listener %s raised an exception!', node, exc_info = True)
